﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RWLibrary.Logic.Statements;
using RWLibrary.Logic.BooleanExpression;
using System.Diagnostics;


namespace RWLibrary.Logic.Graph
{
    class EdgeGenerator
    {
        List<CausesStatement> causes = new List<CausesStatement>();
        List<ReleaseStatement> releases = new List<ReleaseStatement>();
        string ActionName;

        List<State> states;

        public EdgeGenerator(string actionName, StatementManager statemets, List<State> states)
        {
            this.states = states;
            this.ActionName = actionName;
            foreach (CausesStatement cs in statemets.Causes)
            {
                if (cs.ActionFamily.Name == actionName)
                    causes.Add(cs);
            }
            foreach (ReleaseStatement rs in statemets.Release)
            {
                if(rs.ActionFamily.Name == actionName)
                    releases.Add(rs);
            }
        }

        public List<KeyValuePair<Edge,State>> getDestinations(State start)
        {
            List<KeyValuePair<Edge, State>> oldEdges = new List<KeyValuePair<Edge, State>>();

            IBooleanExpression destination = new Truth();
            int ActionCost = 0;
            bool found = false;

            //Wszystkie causes dla danej akcji jako edge
            foreach (CausesStatement cs in causes)
            {
                if (cs.Pi.Test(start)) //akcja do celu
                {
                    
                    destination = new And(destination, cs.Alpha);
                    ActionCost += cs.Cost;
                    found = true;
                }
            }
            foreach (ReleaseStatement rs in releases)
            {
                if (rs.Pi.Test(start)) //akcja do celu
                {
                    ActionCost += rs.Cost;
                }
            }
            if (found)
            {
                List<State> candidates = states.Where(x => destination.Test(x)).ToList();
                if (candidates.Count > 0) //czy istnieje jakis stan do ktorego mozna pojsc
                {
                    //State'ys ktore spelniaja alpha i maja najmniejszy Hamming distance do aktualnie testowanego
                    int minHamming = candidates.Min(x => x.CalculateHammingDistance(start));
                    List<State> minStates = candidates.Where(x => x.CalculateHammingDistance(start) == minHamming).ToList();
                    foreach (State s in minStates)
                    {
                        //State s = states.Where(x => destination.Test(x)).Aggregate((curmin, x) => (curmin == null || x.CalculateHammingDistance(start) < curmin.CalculateHammingDistance(start) ? x : curmin));
                        oldEdges.Add(new KeyValuePair<Edge, State>(new Edge(ActionName, ActionCost, s), s));
                    }
                }
            } else {
                //akcja zerowa
                oldEdges.Add(new KeyValuePair<Edge, State>(new Edge(ActionName, ActionCost, start), start));
                /*if (causes.Count > 0)
                {
                    return oldEdges.Distinct(new EdgeComprarer()).ToList();
                }*/
            }

            //Ogarniecie wszystkich release:
            //wszystkie stare edge z dodatkowym tym fluentem z release
            List<KeyValuePair<Edge, State>> result = oldEdges.ToList();
            foreach (ReleaseStatement rs in releases)
            {
                if (rs.Pi.Test(start))
                {
                    //release dla istniejacych edgow
                    foreach (KeyValuePair<Edge, State> edge in oldEdges)
                    {
                        //ten edge ze zmienionym jednym fluentem
                        State s = new State(edge.Value);
                        s[rs.FluentName] = !edge.Value[rs.FluentName];
                        //Szukamy najblizszych stateow do tego z jednym fluentem zmienionym
                        List<State> candidates = states.Where(x => destination.Test(x) && x[rs.FluentName] == !edge.Value[rs.FluentName]).ToList();
                        if (candidates.Count > 0) //czy istnieje jakis stan do ktorego mozna pojsc
                        {
                            //State'ys ktore spelniaja alpha i maja najmniejszy Hamming distance do aktualnie testowanego
                            int minHamming = candidates.Min(x => x.CalculateHammingDistance(s));
                            List<State> minStates = candidates.Where(x => x.CalculateHammingDistance(s) == minHamming).ToList();
                            foreach (State s2 in minStates)
                            {
                                result.Add(new KeyValuePair<Edge, State>(new Edge(ActionName, ActionCost, s2), s2));
                            }
                        }
                    }
                    oldEdges = result.ToList();
                }
            }
            return result.Distinct(new EdgeComprarer()).ToList();
        }
    }

    class EdgeComprarer : IEqualityComparer<KeyValuePair<Edge, State>>
    {
        public bool Equals(KeyValuePair<Edge, State> x, KeyValuePair<Edge, State> y)
        {
            return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
        }

        public int GetHashCode(KeyValuePair<Edge, State> obj)
        {
            return 7*obj.Key.GetHashCode() + 13 * obj.Value.GetHashCode();
        }
    }
}
